D:\a\tools.proto\tools.proto\compiler\src\gen\template\util.rs
Line | Count | Source |
1 | | // Copyright (c) 2024, BlockProject 3D |
2 | | // |
3 | | // All rights reserved. |
4 | | // |
5 | | // Redistribution and use in source and binary forms, with or without modification, |
6 | | // are permitted provided that the following conditions are met: |
7 | | // |
8 | | // * Redistributions of source code must retain the above copyright notice, |
9 | | // this list of conditions and the following disclaimer. |
10 | | // * Redistributions in binary form must reproduce the above copyright notice, |
11 | | // this list of conditions and the following disclaimer in the documentation |
12 | | // and/or other materials provided with the distribution. |
13 | | // * Neither the name of BlockProject 3D nor the names of its contributors |
14 | | // may be used to endorse or promote products derived from this software |
15 | | // without specific prior written permission. |
16 | | // |
17 | | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
18 | | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
19 | | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
20 | | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
21 | | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
22 | | // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
23 | | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
24 | | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
25 | | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
26 | | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
27 | | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | | |
29 | | use itertools::Itertools; |
30 | | use regex::Regex; |
31 | | use std::borrow::Cow; |
32 | | |
33 | | #[allow(clippy::enum_variant_names)] |
34 | | enum Convention { |
35 | | PascalCase, |
36 | | SnakeCase, |
37 | | ScreamingCase, |
38 | | } |
39 | | |
40 | 129 | fn guess_case_convention(s: &str) -> Convention { |
41 | | //Assume all strings are Rust identifiers following standard Rust conventions: |
42 | | // snake_case, PascalCase and SCREAMING_CASE. |
43 | 129 | let upper1 = (s.as_bytes()[0] >= b'A' && s.as_bytes()[0] <= b'Z') || s.as_bytes()[0] == b'_'38 ; Branch (43:19): [True: 129, False: 0]
Branch (43:46): [True: 91, False: 38]
Branch (43:19): [True: 0, False: 0]
Branch (43:46): [True: 0, False: 0]
|
44 | 129 | let upper2 = (s.as_bytes()[s.as_bytes().len() - 1] >= b'A' && s.as_bytes()[s.as_bytes().len() - 1] <= b'Z'89 ) Branch (44:19): [True: 89, False: 40]
Branch (44:67): [True: 8, False: 81]
Branch (44:19): [True: 0, False: 0]
Branch (44:67): [True: 0, False: 0]
|
45 | 121 | || s.as_bytes()[s.as_bytes().len() - 1] == b'_'; |
46 | 129 | if upper1 && upper291 { Branch (46:8): [True: 91, False: 38]
Branch (46:18): [True: 8, False: 83]
Branch (46:8): [True: 0, False: 0]
Branch (46:18): [True: 0, False: 0]
|
47 | 8 | Convention::ScreamingCase |
48 | 121 | } else if upper1 { Branch (48:15): [True: 83, False: 38]
Branch (48:15): [True: 0, False: 0]
|
49 | 83 | Convention::PascalCase |
50 | | } else { |
51 | 38 | Convention::SnakeCase |
52 | | } |
53 | 129 | } |
54 | | |
55 | 38 | fn capitalize(value: &str) -> Cow<str> { |
56 | 38 | if value.is_empty() { Branch (56:8): [True: 0, False: 38]
Branch (56:8): [True: 0, False: 0]
|
57 | 0 | return value.into(); |
58 | 38 | } |
59 | 38 | if value.as_bytes()[0] >= b'A' && value.as_bytes()[0] <= b'Z' { Branch (59:8): [True: 38, False: 0]
Branch (59:39): [True: 0, False: 38]
Branch (59:8): [True: 0, False: 0]
Branch (59:39): [True: 0, False: 0]
|
60 | 0 | value.into() |
61 | | } else { |
62 | 38 | (value[..1].to_ascii_uppercase() + &value[1..]).into() |
63 | | } |
64 | 38 | } |
65 | | |
66 | 0 | fn decapitalize(value: &str) -> Cow<str> { |
67 | 0 | if value.is_empty() { Branch (67:8): [True: 0, False: 0]
Branch (67:8): [True: 0, False: 0]
|
68 | 0 | return value.into(); |
69 | 0 | } |
70 | 0 | if value.as_bytes()[0] >= b'A' && value.as_bytes()[0] <= b'Z' { Branch (70:8): [True: 0, False: 0]
Branch (70:39): [True: 0, False: 0]
Branch (70:8): [True: 0, False: 0]
Branch (70:39): [True: 0, False: 0]
|
71 | 0 | (value[..1].to_ascii_lowercase() + &value[1..]).into() |
72 | | } else { |
73 | 0 | value.into() |
74 | | } |
75 | 0 | } |
76 | | |
77 | | pub trait CaseConversion<'a> { |
78 | | fn to_pascal_case(self) -> Cow<'a, str>; |
79 | | fn to_snake_case(self) -> Cow<'a, str>; |
80 | | fn to_camel_case(self) -> Cow<'a, str>; |
81 | | fn to_screaming_case(self) -> Cow<'a, str>; |
82 | | } |
83 | | |
84 | | struct SnakeCase<'a>(&'a str); |
85 | | struct PascalCase<'a>(&'a str); |
86 | | struct ScreamingCase<'a>(&'a str); |
87 | | |
88 | | impl<'a> CaseConversion<'a> for SnakeCase<'a> { |
89 | 38 | fn to_pascal_case(self) -> Cow<'a, str> { |
90 | 38 | self.0.split("_").map(capitalize).join("").into() |
91 | 38 | } |
92 | | |
93 | 0 | fn to_snake_case(self) -> Cow<'a, str> { |
94 | 0 | self.0.into() |
95 | 0 | } |
96 | | |
97 | 0 | fn to_camel_case(self) -> Cow<'a, str> { |
98 | 0 | self.0 |
99 | 0 | .split("_") |
100 | 0 | .enumerate() |
101 | 0 | .map(|(i, v)| if i != 0 { capitalize(v) } else { v.into() }) Branch (101:30): [True: 0, False: 0]
Branch (101:30): [True: 0, False: 0]
|
102 | 0 | .join("") |
103 | 0 | .into() |
104 | 0 | } |
105 | | |
106 | 0 | fn to_screaming_case(self) -> Cow<'a, str> { |
107 | 0 | self.0.split("_").map(|v| v.to_uppercase()).join("_").into() |
108 | 0 | } |
109 | | } |
110 | | |
111 | | impl<'a> CaseConversion<'a> for PascalCase<'a> { |
112 | 0 | fn to_pascal_case(self) -> Cow<'a, str> { |
113 | 0 | self.0.into() |
114 | 0 | } |
115 | | |
116 | 28 | fn to_snake_case(self) -> Cow<'a, str> { |
117 | 28 | let regex = Regex::new("[A-Z]([a-z]|[0-9])*").unwrap(); |
118 | 36 | let useless = regex.find_iter(self.0).map(|v| v.as_str().to_lowercase()).join("_").into(); |
119 | 28 | useless |
120 | 28 | } |
121 | | |
122 | 0 | fn to_camel_case(self) -> Cow<'a, str> { |
123 | 0 | decapitalize(self.0) |
124 | 0 | } |
125 | | |
126 | 55 | fn to_screaming_case(self) -> Cow<'a, str> { |
127 | 55 | let regex = Regex::new("[A-Z]([a-z]|[0-9])*").unwrap(); |
128 | 85 | let useless = regex.find_iter(self.0).map(|v| v.as_str().to_uppercase()).join("_").into(); |
129 | 55 | useless |
130 | 55 | } |
131 | | } |
132 | | |
133 | | impl<'a> CaseConversion<'a> for ScreamingCase<'a> { |
134 | 0 | fn to_pascal_case(self) -> Cow<'a, str> { |
135 | 0 | self.0.split("_").map(|v| capitalize(&v.to_lowercase()).to_string()).join("").into() |
136 | 0 | } |
137 | | |
138 | 4 | fn to_snake_case(self) -> Cow<'a, str> { |
139 | 4 | self.0.to_lowercase().into() |
140 | 4 | } |
141 | | |
142 | 0 | fn to_camel_case(self) -> Cow<'a, str> { |
143 | 0 | self.0 |
144 | 0 | .split("_") |
145 | 0 | .enumerate() |
146 | 0 | .map(|(i, v)| { |
147 | 0 | if i != 0 { Branch (147:20): [True: 0, False: 0]
Branch (147:20): [True: 0, False: 0]
|
148 | 0 | format!("{}", capitalize(&v.to_lowercase())) |
149 | | } else { |
150 | 0 | v.into() |
151 | | } |
152 | 0 | }) |
153 | 0 | .join("") |
154 | 0 | .into() |
155 | 0 | } |
156 | | |
157 | 4 | fn to_screaming_case(self) -> Cow<'a, str> { |
158 | 4 | self.0.into() |
159 | 4 | } |
160 | | } |
161 | | |
162 | | macro_rules! impl_case_conversion { |
163 | | ($s: expr, $func: ident) => { |
164 | | match guess_case_convention($s) { |
165 | | Convention::PascalCase => PascalCase($s).$func(), |
166 | | Convention::SnakeCase => SnakeCase($s).$func(), |
167 | | Convention::ScreamingCase => ScreamingCase($s).$func(), |
168 | | } |
169 | | }; |
170 | | } |
171 | | |
172 | | impl<'a> CaseConversion<'a> for &'a str { |
173 | 38 | fn to_pascal_case(self) -> Cow<'a, str> { |
174 | 38 | impl_case_conversion!(self0 , to_pascal_case) |
175 | 38 | } |
176 | | |
177 | 32 | fn to_snake_case(self) -> Cow<'a, str> { |
178 | 32 | impl_case_conversion!(self28 , to_snake_case) |
179 | 32 | } |
180 | | |
181 | 0 | fn to_camel_case(self) -> Cow<'a, str> { |
182 | 0 | impl_case_conversion!(self, to_camel_case) |
183 | 0 | } |
184 | | |
185 | 59 | fn to_screaming_case(self) -> Cow<'a, str> { |
186 | 59 | impl_case_conversion!(self55 , to_screaming_case) |
187 | 59 | } |
188 | | } |